/*
**  Copyright (c) 2014 Michael Liu(michael.liu.point@gmail.com).
**
**  Project: Gateway Unified Management Platform
**  File:      http_redirect.c
**  Author:  Michael
**  Date:    08/04/2014
**
**  Purpose:
**    system http redirect implement.
*/

/* Include files. */

#include "common.h"
#include "dpmgr.h"
#include "eamgr.h"
#include "samgr.h"
#include "osdep.h"
#include "pfdep.h"

/* Macro constant definitions. */

#define SAME_IP_REDIRECT_COUNTER	5

#define IPT_HTTP_ACCEPT_U2M_RULE	"-I PREROUTING -d %s/32 -p tcp -m tcp --dport 80 -j ACCEPT"
#define IPT_HTTP_REDIRECT_RULE		"-A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 12345"

#define HTTP_REDIRECT_PORT			12345

#define HTTP_REDIRECT_RSP_FMT		"HTTP/1.1 302 Moved Temporarily\r\n"\
	"Location: http://%s\r\n"\
	"Server: Apache\r\n"\
	"Content-Type: text/html\r\n"\
	"Content-Length: 0\r\n"\
	"Connection: close\r\n\r\n"

#ifndef SO_REUSEPORT
#define	SO_REUSEPORT				0x0200
#endif

/* Type definitions. */

/* Local function declarations. */
static void *http_redirect_task(void *arg);

static e_ret http_redirect_svc_socket_init(int *http_redirect_socket);
static e_ret http_redirect_svc_socket_clean(int http_redirect_socket);

static e_ret http_redirect_proc(int socket);

/* Macro API definitions. */

/* Global variable declarations. */

static volatile int http_redirect_loop;
static pthread_t http_redirect_thread;

extern tdata_paths *data_paths;

e_ret http_redirect_update_u2m_rule(char *rule_buf, const tdp_ipt_rule_entry * entry)
{
	e_ret ret;
	int rc;
	char local_ip[RC_IPV4_STR_MAX_SIZE];

	ret = os_dep_get_if_ipaddr("lan", local_ip);
	if (ret != eRET_SUCCESS) {
		rule_buf[0] = 0;
		return (eRET_SUCCESS);
	}

	rc = sprintf(rule_buf, entry->ipt_rule, local_ip);
	rule_buf[rc] = '\n';
	rule_buf[rc + 1] = '\0';
	return (eRET_SUCCESS);
}

e_ret http_redirect_start(void)
{

	int rc;

	sysmgr_trace_enter();

	http_redirect_loop = eBOOL_TRUE;

	if (data_paths != NULL) {
		/* Add DNS package capture rule. */
		dp_rules_add_ipt_rule(data_paths, eIPT_RULE_TABLE_NAT, IPT_HTTP_ACCEPT_U2M_RULE,
				      http_redirect_update_u2m_rule);
		dp_rules_add_ipt_rule(data_paths, eIPT_RULE_TABLE_NAT, IPT_HTTP_REDIRECT_RULE, NULL);
		dp_rules_apply_ipt_rule(data_paths);
	}

	rc = pthread_create(&http_redirect_thread, NULL, http_redirect_task, NULL);
	if (rc != 0) {
		sysmgr_err("pthread_create( http_redirect task ) failed, err: %d.\n", rc);
		sysmgr_trace_exit();
		return (eRET_FAILURE);
	}

	sysmgr_trace_exit();
	return (eRET_SUCCESS);

}

e_ret http_redirect_stop(void)
{

	sysmgr_trace_enter();

	http_redirect_loop = eBOOL_FALSE;

	if (data_paths != NULL) {
		/* Delete DNS package capture rule. */
		dp_rules_del_ipt_rule(data_paths, eIPT_RULE_TABLE_NAT, IPT_HTTP_ACCEPT_U2M_RULE);
		dp_rules_del_ipt_rule(data_paths, eIPT_RULE_TABLE_NAT, IPT_HTTP_REDIRECT_RULE);
		dp_rules_apply_ipt_rule(data_paths);
	}

	sysmgr_trace_exit();
	return (eRET_SUCCESS);

}

void *http_redirect_task(void *arg)
{

	e_ret ret;
	int nfds;
	int http_redirect_socket;
	int select_counter;
	fd_set recv_set;
	struct timeval timeout;

	sysmgr_trace_enter();
	sysmgr_dbg("start http_redirect_task()\n");

	ret = http_redirect_svc_socket_init(&http_redirect_socket);
	if (ret != eRET_SUCCESS) {
		sysmgr_err("http_redirect_svc_socket_init() failed.\n");
		goto out;
	}

	/* select socket, proccess when got a message. */
	while (http_redirect_loop) {

		FD_ZERO(&recv_set);
		FD_SET(http_redirect_socket, &recv_set);
		nfds = http_redirect_socket;

		timeout.tv_sec = 1;
		timeout.tv_usec = 0;

		select_counter = select(nfds + 1, &recv_set, NULL, NULL, &timeout);
		if (select_counter <= 0) {
			continue;
		}

		if (FD_ISSET(http_redirect_socket, &recv_set)) {
			http_redirect_proc(http_redirect_socket);
			usleep(10 * 1000);
		}
	}

out:

	http_redirect_svc_socket_clean(http_redirect_socket);

	sysmgr_trace_exit();
	pthread_exit(NULL);
}

e_ret http_redirect_svc_socket_init(int *http_redirect_socket)
{
	int rc;
	int fd;
	int len;
	int on;
	long fd_flags;
	struct sockaddr_in addr;

	sysmgr_trace_enter();

	*http_redirect_socket = 0;
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0) {
		sysmgr_err("socket() failed, err: %d,[%s]\n", errno, strerror(errno));
		return (eRET_FAILURE);
	}

	fd_flags = fcntl(fd, F_GETFD, 0);
	fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);

	fd_flags = fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);

	on = 1;
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));

	on = 1;
	setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on));

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(HTTP_REDIRECT_PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	len = sizeof(struct sockaddr_in);

	rc = bind(fd, (struct sockaddr *)&addr, len);
	if (rc < 0) {
		sysmgr_err("bind() failed, err: %d,[%s]\n", errno, strerror(errno));
		close(fd);
		sysmgr_trace_exit();
		return (eRET_FAILURE);
	}

	rc = listen(fd, 32);
	if (rc < 0) {
		sysmgr_err("listen() failed, err: %d,[%s]\n", errno, strerror(errno));
		close(fd);
		sysmgr_trace_exit();
		return (eRET_FAILURE);
	}

	*http_redirect_socket = fd;

	sysmgr_trace_exit();
	return (eRET_SUCCESS);

}

e_ret http_redirect_svc_socket_clean(int http_redirect_socket)
{

	sysmgr_trace_enter();

	if (http_redirect_socket > 0) {
		close(http_redirect_socket);
	}

	sysmgr_trace_exit();
	return (eRET_SUCCESS);

}

e_ret http_redirect_proc(int socket)
{
	e_ret ret;
	int rc;
	int fd;
	int send_len;
	int msg_len;
	long fd_flags;
	char rx_buff[1024];
	char local_mgr_addr[128];
	char tx_buff[512];
	socklen_t addr_len;
	struct sockaddr_in addr;
	fd_set send_set, recv_set;
	struct timeval timeout;
	struct timeval now;
	static int last_counter;
	static struct sockaddr_in last_addr;
	static struct timeval last_time;
	unsigned long time_pass;

	sysmgr_trace_enter();

	memset(&addr, 0, sizeof(addr));
	memset(&addr_len, 0, sizeof(addr_len));
	fd = accept(socket, (struct sockaddr *)&addr, &addr_len);
	if (fd < 0) {
		sysmgr_err("accept() failed, err: %d,[%s].\n", errno, strerror(errno));
		ret = eRET_FAILURE;
		goto done;
	}

	gettimeofday(&now, NULL);
	time_pass = (now.tv_sec - last_time.tv_sec) * 1000000 + now.tv_usec - last_time.tv_usec;
	sysmgr_dbg("Last[%lu:%lu], Now[%lu:%lu], Pass[%lu]\n",
		   last_time.tv_sec, last_time.tv_usec, now.tv_sec, now.tv_usec, time_pass);
	last_time = now;

	/* Same IP address request in short time */
	if ((time_pass < 100 * 1000) && (last_addr.sin_addr.s_addr == addr.sin_addr.s_addr)) {
		last_counter++;
		if (last_counter == SAME_IP_REDIRECT_COUNTER) {
			last_counter = 0;
		}
	} else {
		last_addr = addr;
		last_counter = 0;
	}

	if (last_counter != 0) {
		ret = eRET_SUCCESS;
		sysmgr_info("Same IP address %d Request, Ignore.\n", last_counter);
		goto done;
	}

	fd_flags = fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);

	do {
		FD_ZERO(&recv_set);
		FD_SET(fd, &recv_set);

		timeout.tv_sec = 0;
		timeout.tv_usec = 500000;

		rc = select(fd + 1, &recv_set, NULL, NULL, &timeout);
		if (rc == 0) {
			ret = eRET_TIMEOUT;
			goto done;
		} else if (rc < 0) {
			/* select be interrupt? */
			if (errno != EINTR) {
				sysmgr_dbg("select() return %d err: %d,[%s]\n", rc, errno, strerror(errno));
				ret = eRET_FAILURE;
				goto done;
			} else {
				continue;
			}
		}

		if (FD_ISSET(fd, &recv_set)) {
			rc = recv(fd, (void *)rx_buff, sizeof(rx_buff), 0);
			if (rc < 0) {
				if ((errno == EAGAIN) || (errno == EINTR)
				    || (errno == EWOULDBLOCK)) {
					continue;
				} else {
					sysmgr_dbg("recv() return %d err: %d,[%s]\n", rc, errno, strerror(errno));
					ret = eRET_FAILURE;
					goto done;
				}
			} else {
				sysmgr_dbg("recv() peer shutdown!\n");
				ret = eRET_FAILURE;
				break;
			}
		}
	} while (1);

#ifdef REDIRECT_TO_IP_ADDR
	ret = os_dep_get_if_ipaddr("lan", local_mgr_addr);
	if (ret != eRET_SUCCESS) {
		sysmgr_dbg("os_dep_get_if_ipaddr() failed, ret %d\n", ret);
		ret = eRET_FAILURE;
		goto done;
	}
#else
	strcpy(local_mgr_addr, PF_DEP_MANAGEMENT_URL);
#endif

	sysmgr_dbg("HTTP redirect to [%s]\n", local_mgr_addr);
	rc = sprintf(tx_buff, HTTP_REDIRECT_RSP_FMT, local_mgr_addr);
	tx_buff[rc] = 0;
	send_len = 0;
	msg_len = strlen(tx_buff);
	do {
		FD_ZERO(&send_set);
		FD_SET(fd, &send_set);
		timeout.tv_sec = 0;
		timeout.tv_usec = 500000;

		rc = select(fd + 1, NULL, &send_set, NULL, &timeout);
		if (rc == 0) {
			ret = eRET_TIMEOUT;
			goto done;
		} else if (rc < 0) {
			/* select be interrupt? */
			if (errno != EINTR) {
				sysmgr_dbg("select() return %d, err: %d,[%s]\n", rc, errno, strerror(errno));
				ret = eRET_FAILURE;
				goto done;
			} else {
				continue;
			}
		}

		if (FD_ISSET(fd, &send_set)) {
			rc = send(fd, (const void *)&tx_buff[send_len], msg_len - send_len, 0);
			if (rc <= 0) {
				if ((errno == EAGAIN) || (errno == EINTR)
				    || (errno == EWOULDBLOCK)) {
					continue;
				} else {
					sysmgr_err("send() return %d, err: %d,[%s]\n", rc, errno, strerror(errno));
					ret = eRET_FAILURE;
					goto done;
				}
			} else {
				send_len += rc;
			}
		}
	} while (send_len < msg_len);

done:

	if (fd > 0) {
		close(fd);
	}

	sysmgr_trace_exit();
	return (ret);

}
